package com.lxc.demos.util.c2dm;

import android.app.AlarmManager;
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import android.util.Log;

/**
 * Base class for C2D message receiver. Includes constants for the strings used
 * in the protocol. 参考：http://code.google.com/intl/zh-CN/android/c2dm/
 */
public abstract class C2DMBaseReceiver extends IntentService {

	/**
	 * 和C2DM Push的Intent 内容相关 重新向C2DM服务器注册
	 */
	private static final String C2DM_RETRY = "com.google.android.c2dm.intent.RETRY";

	/**
	 * 收到注册消息(注册ID)
	 */
	private static final String REGISTRATION_CALLBACK_INTENT = "com.google.android.c2dm.intent.REGISTRATION";

	/**
	 * 收到推送消息
	 */
	private static final String C2DM_INTENT = "com.google.android.c2dm.intent.RECEIVE";

	// Logging tag
	private static final String TAG = "C2DM";

	// Extras in the registration callback intents.
	// 向C2DM注册返回的Intent中包含的key
	public static final String EXTRA_UNREGISTERED = "unregistered";
	public static final String EXTRA_ERROR = "error";
	public static final String EXTRA_REGISTRATION_ID = "registration_id";

	/* 向C2DM注册出错的原因 */
	// The device can't read the response, or there was a 500/503 from the
	// server that can be retried later. The application should use exponential
	// back off and retry.
	public static final String ERR_SERVICE_NOT_AVAILABLE = "SERVICE_NOT_AVAILABLE";
	// There is no Google account on the phone. The application should ask the
	// user to open the account manager and add a Google account. Fix on the
	// device side.
	public static final String ERR_ACCOUNT_MISSING = "ACCOUNT_MISSING";
	// Bad password. The application should ask the user to enter his/her
	// password, and let user retry manually later. Fix on the device side.
	public static final String ERR_AUTHENTICATION_FAILED = "AUTHENTICATION_FAILED";
	// The user has too many applications registered. The application should
	// tell the user to uninstall some other applications, let user retry
	// manually. Fix on the device side.
	public static final String ERR_TOO_MANY_REGISTRATIONS = "TOO_MANY_REGISTRATIONS";

	public static final String ERR_INVALID_PARAMETERS = "INVALID_PARAMETERS";
	// The sender account is not recognized.
	public static final String ERR_INVALID_SENDER = "INVALID_SENDER";
	// Incorrect phone registration with Google. This phone doesn't currently
	// support C2DM.
	public static final String ERR_PHONE_REGISTRATION_ERROR = "PHONE_REGISTRATION_ERROR";

	// wakelock
	private static final String WAKELOCK_KEY = "C2DM_LIB";

	/**
	 * Wake Lock是一种锁的机制, 只要有人拿着这个锁,系统就无法进入休眠， 可以被用户态程序和内核获得. 这个锁可以是有超时的或者是没有超时的,
	 * 超时的锁会在时间过去以后自动解锁. 如果没有锁了或者超时了, 内核就 会启动休眠的那套机制来进入休眠. PowerManager.WakeLock
	 * 有加锁和解锁两种状态，加锁的方式有两种，一种是永久的锁住，这样的锁除非显式的放开，是不会解锁的，所以这种锁用起来要非常的小心。第二种锁是超时锁，
	 * 这种锁会在锁住后一段时间解锁。
	 * 
	 * 在创建了 PowerManager.WakeLock 后，有两种机制，第一种是不计数锁机制， 另一种是计数锁机制。可以通过
	 * setReferenceCounted(boolean value) 来指定， 一般默认为计数机制。这两种机制的区别在于，前者无论
	 * acquire() 了多少次， 只要通过一次 release()即可解锁。而后者正真解锁是在（ --count == 0 ）的时候， 同样当
	 * (count == 0) 的时候才会去申请加锁，其他情况 isHeld 状态是不会改变的。 所以 PowerManager.WakeLock
	 * 的计数机制并不是正真意义上的对每次请求进行申请／释放每一把锁， 它只是对同一把锁被申请／释放的次数进行了统计再正真意义上的去操作。
	 * 一下进行了永久锁的测试： 从测试我们可以看到使用计数和计数锁的区别。
	 */
	private static PowerManager.WakeLock mWakeLock;
	private final String senderId;

	/**
	 * The C2DMReceiver class must create a no-arg constructor and pass the
	 * sender id to be used for registration
	 * 
	 * @param senderId
	 */
	public C2DMBaseReceiver(String senderId) {
		// senderId is used as base name for threads, etc.
		super(senderId);
		this.senderId = senderId;

	}

	@Override
	public final void onHandleIntent(Intent intent) {
		Log.d(TAG,"C2DMBaseReceiver.onHandleIntent.");
		try {
			Context context = getApplicationContext();
			if (intent.getAction().equals(REGISTRATION_CALLBACK_INTENT)) {
				/* 收到注册ID */
				handleRegistration(context, intent);
			} else if (intent.getAction().equals(C2DM_INTENT)) {
				/* 收到注册消息 */
				onMessage(context, intent);
			} else if (intent.getAction().equals(C2DM_RETRY)) {
				/* 请求获取注册ID */
				C2DMessaging.register(context, senderId);
			}
		} finally {
			// Release the power lock,so phone can get back to sleep.
			// The lock is reference counted ty default,so multiple
			// messages are ok.

			// If the onMessage() needs to spawn a thread or do something else,
			// it should use it's own lock.

			mWakeLock.release();
		}

	}

	/**
	 * Called when a cloud message has been received.
	 * 接收到C2DM服务器Push消息后的回调函数，需要在继承的子类中处理
	 * 
	 * @param context
	 * @param intent
	 */
	protected abstract void onMessage(Context context, Intent intent);

	private void handleRegistration(Context context, Intent intent) {
		Log.d(TAG,"C2DMBaseReceiver.handleRegistration.");

		final String registrationId = intent
				.getStringExtra(EXTRA_REGISTRATION_ID);
		String error = intent.getStringExtra(EXTRA_ERROR);
		String removed = intent.getStringExtra(EXTRA_UNREGISTERED);
		// 打印出接收到的registraton_id
		Log.d(TAG, "dmControl: registrationId = " + registrationId
				+ ", error = " + error + ", removed = " + removed);

		if (removed != null) {
			// Remember we are unregistered
			C2DMessaging.clearRegistrationId(context);
			onUnregistered(context);
			return;
		} else if (error != null) {
			// we are not registered,can try again
			C2DMessaging.clearRegistrationId(context);
			Log.e(TAG, "Registration error " + error);
			onError(context, error);
			if (ERR_SERVICE_NOT_AVAILABLE.equals(error)) {
				long backoffTimeMs = C2DMessaging.getBackoff(context);

				Log.d(TAG, "Scheduling registration retry, backoff = "
						+ backoffTimeMs);
				Intent retryIntent = new Intent(C2DM_RETRY);
				PendingIntent retryPIntent = PendingIntent.getBroadcast(
						context, 0/* requestCode */, retryIntent, 0/* flags */);
				/*
				 * AlarmManager的使用机制有的称呼为全局定时器，有的称呼为闹钟。
				 * 通过对它的使用，个人觉得叫全局定时器比较合适，其实它的作用和Timer有点相似。
				 * 都有两种相似的用法：（1）在指定时长后执行某项操作（2）周期性的执行某项操作
				 * AlarmManager对象配合Intent使用，可以定时的开启一个Activity,发送一个BroadCast,
				 * 或者开启一个Service.
				 */
				AlarmManager am = (AlarmManager) context
						.getSystemService(Context.ALARM_SERVICE);
				am.set(AlarmManager.ELAPSED_REALTIME, backoffTimeMs,
						retryPIntent);

				// Next retry should wait longer.
				backoffTimeMs *= 2;
				C2DMessaging.setBackoff(context, backoffTimeMs);
			}
		} else {
			onRegistered(context, registrationId);
			C2DMessaging.setRegistrationId(context, registrationId);//保存registrationId
		}
	}

	/**
	 * Called when a registration token has been received. 注册后的回调函数
	 */
	public void onRegistered(Context context, String registrationId) {
		// registrationId will also be saved

	}

	/**
	 * Called on registration error. Override to provide better error messages.
	 * 
	 * This is called in the context of a Service - no dialog or UI.
	 */
	public abstract void onError(Context context, String error);

	/**
	 * Called when the device has been unregistered.
	 * 
	 * @param context
	 */
	public void onUnregistered(Context context) {
		// TODO Auto-generated method stub

	}

	/**
	 * Called from the broadcast receiver. Will process the received intent,
	 * call handleMessage(), registered(), etc. in background threads, with a
	 * wake lock, while keeping the service alive.
	 */
	static void runIntentInService(Context context, Intent intent) {
		Log.d(TAG,"C2DMBaseReceiver.runIntentInService");
		if (mWakeLock == null) {
			PowerManager pm = (PowerManager) context
					.getSystemService(Context.POWER_SERVICE);
			mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
					WAKELOCK_KEY);
		}
		mWakeLock.acquire();

		// Use a naming convention, similar with how permissions and intents are
		// used. Alternatives are introspection or an ugly use of statics.
		String receiver = context.getPackageName() + ".C2DMReceiver";
		intent.setClassName(context, receiver);

		Log.d(TAG,"receiver = "+receiver);
		context.startService(intent);
	}

}
